var rocon = (function() {/**
 * Общие методы и свойства для rocon
 * @author Sergey Chikuyonok (sc@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 */

    var re_rule = /\.rc(\d+)\b/,
	re_class = /\brc(\d+)\b/,
	re_shape_flag = /\brc-shape\b/,

    /** Префиск для создаваемых CSS-правил */
	rule_prefix = 'rocon__',

    /** Базовый класс для создаваемых элементов */
	base_class = 'rocon',

    /** Привязанные к определенным классам фоны */
	binded_props = [],

    /** Результат, возвращаемый в объект <code>rocon</code> */
	result = {
	    /**
	    * Добавление/обновление уголков для динамически созданных элементов.
	    * Может принимать неограниченное количество элементов либо массивов
	    * элементов, у которых нужно обновить уголки  
	    */
	    update: function() { },
	    bindProperties: function() {
	        var id = 1;
	        return function(rule, bg, border_width) {
	            binded_props.push({
	                'id': id++,
	                'rule': rule,
	                'bg': mapArray(expandProperty(bg), function(val) {
	                    if (val.charAt(0) != '#')
	                        val = '#' + val;
	                    return convertColorToHex(val);
	                }),
	                'border_width': border_width || 0
	            });
	        }
	    } (),

	    process: function(context) {
	        processRoundedElements(context);
	    }
	},

    /** @type {CSSStyleSheet} Таблица стилей для уголков */
	corners_ss = null,

    /** Кэш для уголков */
	_corner_cache = {},

    /** Классы элементов, которым нужно добавить скругленные уголки */
	elem_classes = [],

    /** Список функций, которые нужно выполнить при загрузке DOM-дерева */
	dom_ready_list = [],

    /** Загрузился ли DOM? */
	is_ready = false,

    /** Привязано ли событие, ожидающее загрузку DOM? */
	readyBound = false,

	userAgent = navigator.userAgent.toLowerCase(),

    /** 
    * CSS-селекторы, которые уже были добавлены в стили. 
    * Используется для того, чтобы не создавать одинаковые правила
    */
	processed_rules = {},

    /** Тип и версия браузера пользователя. Взято с jQuery */
	browser = {
	    version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
	    safari: /webkit/.test(userAgent),
	    opera: /opera/.test(userAgent),
	    msie: /msie/.test(userAgent) && !/opera/.test(userAgent),
	    mozilla: /mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent)
	};

    /**
    * Выполняет все функции, добавленные на событие onDomContentLoaded.
    * Взято с jQuery
    */
    function fireReady() {
        //Make sure that the DOM is not already loaded
        if (!is_ready) {
            // Remember that the DOM is ready
            is_ready = true;

            // If there are functions bound, to execute
            if (dom_ready_list.length) {

                for (var i = 0; i < dom_ready_list.length; i++) {
                    dom_ready_list[i].call(document);
                }

                //			walkArray(dom_ready_list, function(){
                //				this.call(document);
                //			});

                // Reset the list of functions
                dom_ready_list = null;
            }
        }
    }

    /**
    * Добавляет слушателя на событие onDomContentLoaded
    * @type {Function} fn Слушатель
    */
    function addDomReady(fn) {
        dom_ready_list.push(fn);
    }

    /**
    * Проверка на наступление события onDomContentLoaded. 
    * Взято с jQuery
    */
    function bindReady() {
        if (readyBound) return;
        readyBound = true;

        // Mozilla, Opera and webkit nightlies currently support this event
        if (document.addEventListener) {
            // Use the handy event callback
            document.addEventListener("DOMContentLoaded", function() {
                document.removeEventListener("DOMContentLoaded", arguments.callee, false);
                fireReady();
            }, false);

            // If IE event model is used
        } else if (document.attachEvent) {
            // ensure firing before onload,
            // maybe late but safe also for iframes
            document.attachEvent("onreadystatechange", function() {
                if (document.readyState === "complete") {
                    document.detachEvent("onreadystatechange", arguments.callee);
                    fireReady();
                }
            });

            // If IE and not an iframe
            // continually check to see if the document is ready
            if (document.documentElement.doScroll && !window.frameElement) (function() {
                if (is_ready) return;

                try {
                    // If IE is used, use the trick by Diego Perini
                    // http://javascript.nwbox.com/IEContentLoaded/
                    document.documentElement.doScroll("left");
                } catch (error) {
                    setTimeout(arguments.callee, 0);
                    return;
                }

                // and execute any waiting functions
                fireReady();
            })();
        }
    }

    /**
    * Вспомогательная функция, которая пробегается по всем элементам массива
    * <code>ar</code> и выполняет на каждом элементе его элементе функцию
    * <code>fn</code>. <code>this</code> внутри этой функции указывает на 
    * элемент массива
    * @param {Array} ar Массив, по которому нужно пробежаться
    * @param {Function} fn Функция, которую нужно выполнить на каждом элементе массива
    * @param {Boolean} forward Перебирать значения от начала массива (п умолчанию: с конца)
    */
    function walkArray(ar, fn, forward) {
        if (forward) {
            for (var i = 0, len = ar.length; i < len; i++)
                if (fn.call(ar[i], i) === false)
                break;
        } else {
            for (var i = ar.length - 1, result; i >= 0; i--)
                if (fn.call(ar[i], i) === false)
                break;
        }
    }

    /**
    * Преобразует один массив элементов в другой с помощью функции callback.
    * Взято в jQuery
    * @param {Array} elems
    * @param {Function} callback
    * @return {Array}
    */
    function mapArray(elems, callback) {
        var ret = [];

        // Go through the array, translating each of the items to their
        // new value (or values).
        for (var i = 0, length = elems.length; i < length; i++) {
            var value = callback(elems[i], i);

            if (value != null)
                ret[ret.length] = value;
        }

        return ret.concat.apply([], ret);
    }

    /**
    * Функция добавления скругленных уголков элементу. Для каждого браузера 
    * будет своя функция
    */
    function addCorners() {
        return;
    };

    // TODO Добавить исключение при правильной работе border-radius

    /**
    * Преобразует цвет из RGB-предствления в hex
    * @param {String} color
    * @return {String}
    */
    function convertColorToHex(color) {
        var result;
        function s(num) {
            var n = parseInt(num, 10).toString(16);
            return (n.length == 1) ? n + n : n;
        }

        function p(num) {
            return s(Math.round(num * 2.55));
        }

        if (result = /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/.exec(color))
            return '#' + s(result[1]) + s(result[2]) + s(result[3]);

        // Look for rgb(num%,num%,num%)
        if (result = /rgb\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*\)/.exec(color))
            return '#' + p(result[1]) + p(result[2]) + p(result[3]);

        // Look for #a0b1c2
        if (result = /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i.exec(color))
            return '#' + result[1] + result[2] + result[3];

        if (result = /#([a-f0-9])([a-f0-9])([a-f0-9])/i.exec(color))
            return '#' + result[1] + result[1] + result[2] + result[2] + result[3] + result[3];

        s = null;
        p = null;

        return color;
    }

    /**
    * Создает HTML-элемент <code>name</code> с классом <code>class_name</code>
    * @param {String} name Название элемента
    * @param {String} class_name Класс элемента
    * @return {Element}
    */
    function createElement(name, class_name) {
        var elem = document.createElement(name);
        if (class_name) {
            elem.className = class_name;
        }
        return elem;
    }

    /**
    * Простая проверка наличия определенного класса у элемента
    * @param {HTMLElement} elem
    * @param {String} class_name
    * @return {Boolean}
    */
    function hasClass(elem, class_name) {
        var re = new RegExp('\\b' + class_name + '\\b');
        return elem.nodeType == 1 && re.test(elem.className || '');
    }

    /**
    * Возвращает значение CSS-свойства <b>name</b> элемента <b>elem</b>
    * @author John Resig (http://ejohn.org)
    * @param {Element} elem Элемент, у которого нужно получить значение CSS-свойства
    * @param {String|Array} name Название CSS-свойства
    * @return {String|Object}
    */
    function getStyle(elem, name) {
        var cs,
		result = {},
		camel = function(str, p1) { return p1.toUpperCase(); };

        walkArray(name instanceof Array ? name : [name], function() {
            var n = this,
			name_camel = n.replace(/\-(\w)/g, camel);

            // If the property exists in style[], then it's been set
            // recently (and is current)
            if (elem.style[name_camel]) {
                result[name_camel] = elem.style[name_camel];
            }
            //Otherwise, try to use IE's method
            else if (browser.msie) {
                result[name_camel] = elem.currentStyle[name_camel];
            }
            // Or the W3C's method, if it exists
            else if (document.defaultView && document.defaultView.getComputedStyle) {
                if (!cs)
                    cs = document.defaultView.getComputedStyle(elem, "");
                result[name_camel] = cs && cs.getPropertyValue(n);
            }
        });

        return name instanceof Array ? result : result[name.replace(/\-(\w)/g, camel)];

    }

    /**
    * Разворачивает краткую запись четырехзначного свойства в полную:<br>
    * 	— a      -&gt; a,a,a,a<br>
    *	— a_b    -&gt; a,b,a,b<br>
    *	— a_b_с  -&gt; a,b,с,b<br>
    * 
    * @param {String} prop Значение, которое нужно раскрыть
    * @return {Array} Массив с 4 значениями
    */
    function expandProperty(prop) {
        var chunks = (prop || '').split('_');

        switch (chunks.length) {
            case 1:
                return [chunks[0], chunks[0], chunks[0], chunks[0]];
            case 2:
                return [chunks[0], chunks[1], chunks[0], chunks[1]];
            case 3:
                return [chunks[0], chunks[1], chunks[2], chunks[1]];
            case 4:
                return chunks;
        }

        return null;
    }

    /**
    * Возвращает цвет фона элемента
    * @type {Function}
    * @param {Element} elem Элемент, для которого нужно достать цвет фона
    * @param {Boolean} use_shape Для элемента создаются уголки в виде формы
    * @return {Array} Массив из 4 элементов фона
    */
    var getBg = (function() {

        var session_elems = [],
		default_color = '#ffffff';

        /**
        * Основной цикл с использованием кэширования
        */
        function mainLoopCache(elem) {
            var c;
            do {
                if (elem.nodeType != 1)
                    break;

                if (elem.rocon_bg) { // цвет был найден ранее
                    return elem.rocon_bg;
                } else { // цвет еще не найден
                    session_elems.push(elem);
                    c = getStyle(elem, 'background-color');
                    if (c != 'transparent')
                        return convertColorToHex(c);
                }

            } while (elem = elem.parentNode);

            return default_color;
        }

        /**
        * Основной цикл без кэширования
        */
        function mainLoopNoCache(elem) {
            var c;
            do {
                if (elem.nodeType != 1)
                    break;

                c = getStyle(elem, 'background-color');
                if (c != 'transparent')
                    return convertColorToHex(c);

            } while (elem = elem.parentNode);

            return default_color;
        }

        return function(elem, use_shape) {
            var cl = /* String */elem.className,
			bg = null;

            // сначала посмотрим, указан ли фон в классе элемента
            var bg_props = /\brcbg([a-f0-9_]+)\b/i.exec(cl);

            if (bg_props) {

                bg = mapArray(expandProperty(bg_props[1]), function(el) {
                    return convertColorToHex('#' + el);
                });

                return bg;
            }

            // Теперь проверяем, есть ли привязанный через rocon.bindBg() к классу фон
            var elem_props = getBindedProperties(elem);
            if (elem_props) {
                return elem_props.bg;
            }

            if (!use_shape)
                elem = elem.parentNode;

            if (getBg.use_cache) {
                session_elems = [];
                bg = mainLoopCache(elem);
                // закэшируем цвет фона у всех элементов, по которым проходились
                walkArray(session_elems, function() {
                    this.rocon_bg = bg;
                    getBg.processed_elems.push(this);
                });

                session_elems = null;
            } else {
                bg = mainLoopNoCache(elem);
            }

            return expandProperty(bg);
        }
    })();

    getBg.use_cache = true;
    getBg.processed_elems = [];

    function getBindedProperties(elem) {
        var cl = elem.className, result = null;
        walkArray(binded_props, function() {
            if (
            // проверка наличия подстроки
			(typeof (this.rule) == 'string' && cl.indexOf(this.rule) != -1) ||
            // проверка по регулярке
			cl.search(this.rule) != -1
		) {
                result = this;
                return false;
            }
        }, true);

        return result;
    }

    /**
    * Добавляет CSS-правило в стиль
    * @param {String} selector CSS-селектор, для которого нужно добавить правила
    * @param {String} rules CSS-правила
    */
    function addRule(selector, rules) {
        corners_ss.insertRule(selector + ' {' + rules + '}', corners_ss.cssRules.length);
    }

    /**
    * Функция поиска правил для скругленных уголков
    * @param {Function} addFunc Функция добавления уголков
    */
    function findRules(addFunc) {
        /** @type {String[]}  */
        var match;

        walkArray(document.styleSheets, function() {
            walkArray(this.cssRules || this.rules, function() {
                if (match = re_rule.exec(this.selectorText))
                    addFunc(this, parseInt(match[1], 10));
            });
        });
    }

    /**
    * Очищает элемент от предыдущих вставок скругленных уголков
    * @param {Element} elem
    * @param {String} [add_class] Классы, которые нужно добавить
    * @return {Element} Переданный элемент
    */
    function cleanUp(elem, add_class) {
        var cl = (elem.className || '').replace(new RegExp('\\s*' + base_class + '[\-_].+?\\b', 'ig'), '');
        if (add_class) {
            cl += ' ' + add_class;
        }

        elem.className = cl;
        return elem;
    }


    /**
    * Функция добавления правил для скругленных уголков
    */
    function addRoundedProperties(/* CSSStyleRule */rule, /* Number */radius) {
        elem_classes.push(rule.selectorText.substr(1));
    }

    /**
    * Создает новую таблицу стилей на странице, куда будут добавляться правила
    * для описания скругленных уголков
    * @return {CSSStyleSheet}
    */
    function createStylesheet() {
        if (!corners_ss) {
            if (document.createStyleSheet) {
                corners_ss = document.createStyleSheet();
            } else {
                var style = createElement('style');
                style.rel = 'rocon';
                document.getElementsByTagName('head')[0].appendChild(style);

                /*
                * Просто получить самый последний стиль не получится: иногда стили
                * добавляются внутрь <body> (так делает счетчик Яндекса, например),
                * в этом случае мы не можем быть уверены, что только что 
                * добавленная таблица стилей — последняя. Поэтому пробегаетмся 
                * по всем таблицам в поисках нашей  
                */
                walkArray(document.styleSheets, function() {
                    if (this.ownerNode.rel == 'rocon') {
                        corners_ss = this;
                        return false;
                    }
                });
            }
        }

        return corners_ss;
    }

    /**
    * Возвращает массив элементов, которым нужно добавить скругленные уголки.
    * Элементом массива является объект со свойствами <code>node</code> 
    * и <code>radius</code>
    * @param {Element} [context] Откуда брать элементы
    * @return {Array}
    */
    function getElementsToProcess(context) {
        var elems = [], m;

        walkArray((context || document).getElementsByTagName('*'), function() {
            if (m = re_class.exec(this.className || '')) {
                elems.push({ node: this, radius: parseInt(m[1], 10) });
            }
        });

        return elems;
    }

    /**
    * Обрабатывает все элементы на странице, которым нужно добавить скругленные
    * уголки
    */
    function processRoundedElements(context) {
        var elems = getElementsToProcess(context);
        if (elems.length) {
            createStylesheet();
            walkArray(elems, function() {
                addCorners(this.node, this.radius);
            });
        }
    }
    /**
    * Проверяет, был ли добавлен CSS-selector в таблицу стилей
    * @param {String} selector
    * @return {Boolean}
    */
    function isProcessed(selector) {
        return processed_rules[selector] ? true : false;
    }

    /**
    * Возвращает параметры уголка элемента
    * @param {Element} elem Элемент, у которого нужно получить параметры уголка
    * @param {Number} [radius] Радиус скругления
    */
    function getCornerParams(elem, radius) {
        var cl = elem.className || '';
        radius = radius || parseInt(cl.match(re_class)[1], 10);
        var use_shape = re_shape_flag.test(cl),
		props = getBindedProperties(elem);

        var border_color = '';
        var border_width = props ? props.border_width : (parseInt(getStyle(elem, 'border-left-width')) || 0);
        if (border_width) {
            // нужно отрисовать бордюр
            border_color = convertColorToHex(getStyle(elem, 'border-left-color') || '#000');
        }

        return {
            'radius': radius,
            'bg_color': getBg(elem, use_shape),

            // толщина бордюра не может быть больше радиуса скругления
            // (так по CSS3 спецификации)
            'border_width': (border_width > radius) ? radius : border_width,
            'real_border_width': border_width,
            'border_color': border_color,
            'use_shape': use_shape
        };
    }

    /**
    * Применяет уголки к элементам, переданным в массиве. В основном вызывается из
    * <code>rocon.update()</code>
    * @param {arguments} args Аргументы функции
    * @param {Function} fn Функция, которую нужно выполнить на каждом элементе
    */
    function applyCornersToArgs(args, fn) {
        walkArray(args, function() {
            walkArray((this instanceof Array) ? this : [this], fn);
        });
    }

    /**
    * Делает копию объекта
    * @param {Object} obj
    * @return {Object}
    */
    function copyObj(obj) {
        var result = {};
        for (var p in obj)
            if (obj.hasOwnProperty(p))
            result[p] = obj[p];

        return result;
    }

    /**
    * Корректирует CSS-свойства элемента для правильного рисования уголков в виде
    * формы
    * @param {HTMLElement} elem Элемент, который нужно подкорректировать
    * @param {String} class_name Имя создаваемого класса
    * @param {getCornerParams()} options параметры рисования уголка
    */
    function adjustBox(elem, class_name, options) {
        var elem_styles = getStyle(elem, ['padding-top', 'padding-bottom', 'margin-top', 'margin-bottom']);
        function getProp(prop) {
            return parseInt(elem_styles[prop], 10) || 0;
        }

        /*
        * Используем форму, поэтому у блока снижаем верхние и нижние
        * бордюры, а также на величину радиуса снижаем верхний 
        * и нижний паддинг 
        */

        var padding_top = Math.max(getProp('paddingTop') - options.radius + options.border_width, 0),
		padding_bottom = Math.max(getProp('paddingBottom') - options.radius + options.border_width, 0),
		margin_top = getProp('marginTop') + options.radius,
		margin_bottom = getProp('marginBottom') + options.radius,
		border_width = options.real_border_width - options.border_width;

        addRule('.' + class_name,
			'border-top-width:' + border_width + 'px;' +
			'border-bottom-width:' + border_width + 'px;' +
			'padding-top:' + padding_top + 'px;' +
			'padding-bottom:' + padding_bottom + 'px;' +
			'margin-top:' + margin_top + 'px;' +
			'margin-bottom:' + margin_bottom + 'px');
    }

    addDomReady(processRoundedElements);
    // после того, как добавили уголки, необходимо очистить кэш фона,
    // иначе будут проблемы с динамическим обновлением блоков
    addDomReady(function() {
        walkArray(getBg.processed_elems, function() {
            this.removeAttribute('rocon_bg');
        });
        getBg.use_cache = false;
    });

    bindReady(); /**
 * Добавление уголков для Safari
 * @author Sergey Chikuyonok (sc@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 * @include "common.js"
 */

    if (browser.safari) {
        addCorners = function(elem, radius) {
            var selector = '.rc' + radius;
            if (!isProcessed(selector)) {
                addRule(selector, '-webkit-border-radius:' + radius + 'px; -khtml-border-radius:' + radius);
                processed_rules[selector] = true;
            }
        }

        result.update = function() {
            applyCornersToArgs(arguments, function() {
                var m = re_class.exec(this.className || '');
                if (m)
                    addCorners(this, parseInt(m[1]));
            });
        }
    } /**
 * Добавление уголков для Firefox
 * @author Sergey Chikuyonok (sc@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 * @include "common.js"
 */

    if (browser.mozilla) {
        addCorners = function(elem, radius) {
            var selector = '.rc' + radius;
            if (!isProcessed(selector)) {
                addRule(selector, '-moz-border-radius:' + radius + 'px');
                processed_rules[selector] = true;
            }
        }

        result.update = function() {
            applyCornersToArgs(arguments, function() {
                var m = re_class.exec(this.className || '');
                if (m)
                    addCorners(this, parseInt(m[1]));
            });
        }
    } /**
 * Добавление уголков для Opera
 * @author Sergey Chikuyonok (sc@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 * @include "common.js"
 * @include "/js-libs/canvas-doc.js"
 */

    if (browser.opera) {
        /*
        * Нужно дожаться, пока загрузится DOM-дерево, после чего получить все 
        * элементы, которым нужно скруглить уголки, и добавить соотвествующие 
        * стили и элементы
        */

        createStylesheet();
        addRule('.' + base_class, 'position:absolute;background-repeat:no-repeat;z-index:1;display:none');
        addRule('.' + base_class + '-init', 'position:relative;');
        addRule('.' + base_class + '-init>.' + base_class, 'display:inline-block;');
        addRule('.' + base_class + '-tl', 'top:0;left:0;background-position:100% 100%;');
        addRule('.' + base_class + '-tr', 'top:0;right:0;background-position:0 100%;');
        addRule('.' + base_class + '-bl', 'bottom:0;left:0;background-position:100% 0;');
        addRule('.' + base_class + '-br', 'bottom:0;right:0;');

        /** @type {HTMLCanvasElement} Холст, на котором будут рисоваться уголки */
        var cv = createElement('canvas');

        /**
        * Возвращает подготовленный контекст рисования на холсте
        * @param {getCornerParams()} options Параметры рисования уголка
        * @param {Boolean} is_shape Будем рисовать форму (true) или контр-форму (false)?
        * @return {CanvasRenderingContext2D}
        */
        function getDrawingContext(options) {
            options.border_width = (options.border_width > options.radius)
				? options.radius
				: options.border_width;

            if (options.border_width > 1)
                options.radius -= options.border_width / 2;


            var width = options.radius * 2 + options.border_width, height = width;
            if (options.use_shape) {
                width = 2000;
                if (options.border_width < options.real_border_width) {
                    height += (options.real_border_width - options.border_width) * 2;
                }
            }


            if (options.border_width == 1) {
                width--;
                height--;
            }

            cv.width = options.width = width;
            cv.height = options.height = height;

            /** @type {CanvasRenderingContext2D} */
            var ctx = cv.getContext('2d');

            ctx.strokeStyle = options.border_color;
            ctx.lineWidth = options.border_width;
            ctx.lineJoin = 'miter';
            ctx.lineCap = 'square';
            ctx.fillStyle = options.bg_color[0];

            ctx.clearRect(0, 0, width, height);
            return ctx;
        }

        /**
        * Делает обводку в виде звездочки
        * @param {CanvasRenderingContext2D} ctx Контекст рисования
        * @param {Number} options.radius Радиус скругления
        * @param {String} options.color Цвет уголка в hex-формате
        * @param {Number} options.border_width Толщина обводки
        * @param {String} options.border_color Цвет обводки
        */
        function strokeStar(ctx, options) {
            var deg90 = Math.PI / 2,
			b2 = (options.border_width > 1) ? options.border_width : 0,
			rb2 = options.radius * 2 + b2;

            ctx.beginPath();
            ctx.arc(0, 0, options.radius, deg90, 0, true);
            ctx.stroke();

            ctx.beginPath();
            ctx.arc(rb2, 0, options.radius, deg90 * 2, deg90, true);
            ctx.stroke();

            ctx.beginPath();
            ctx.arc(rb2, rb2, options.radius, -deg90, deg90 * 2, true);
            ctx.stroke();

            ctx.beginPath();
            ctx.arc(0, rb2, options.radius, 0, -deg90, true);
            ctx.stroke();
        }

        /**
        * Рисует «звездочку» для создания формы уголков через canvas
        * @param {Number} options.radius Радиус скругления
        * @param {String} options.color Цвет уголка в hex-формате
        * @param {Number} options.border_width Толщина обводки
        * @param {String} options.border_color Цвет обводки
        * @return {String} Картинка в формате data:URL
        */
        function drawStarShape(options) {
            options = copyObj(options);

            var ctx = getDrawingContext(options),
			deg90 = Math.PI / 2,
			deg360 = Math.PI * 2,
			bw = options.border_width,
			b2 = (bw > 1) ? bw : 0,
			rb2 = options.radius * 2 + b2,
			diff = 0,
			draw_borders = (options.border_width < options.real_border_width);

            var drawCircle = function(x, y) {
                ctx.beginPath();
                ctx.arc(x, y, options.radius, 0, deg360, true);
                ctx.closePath();
                ctx.fill();
            }

            if (draw_borders) {
                // нужно дорисовать толщину бордера
                diff = options.real_border_width - options.border_width;
                ctx.save();
                ctx.translate(0, diff);
            }

            drawCircle(0, 0);
            drawCircle(rb2, 0);
            drawCircle(rb2, rb2);
            drawCircle(0, rb2);

            ctx.fillRect(rb2, 0, options.width, options.height);

            if (bw) {
                strokeStar(ctx, options);
                ctx.fillStyle = ctx.strokeStyle;
                ctx.fillRect(rb2, options.radius - (bw > 1 ? bw / 2 : bw), options.width, bw * 2);

                if (draw_borders) {
                    ctx.restore();
                    ctx.fillStyle = options.border_color;
                    ctx.fillRect(0, 0, options.width, diff);
                    ctx.fillRect(0, options.height - diff, options.width, diff);
                    ctx.fillStyle = options.bg_color;
                }
            }

            return ctx.canvas.toDataURL();
        }

        /**
        * Рисует «звездочку» через canvas
        * @param {Number} options.radius Радиус скругления
        * @param {String} options.color Цвет уголка в hex-формате
        * @param {Number} options.border_width Толщина обводки
        * @param {String} options.border_color Цвет обводки
        * @return {String} Картинка в формате data:URL
        */
        function drawStar(options) {
            var old_opt = options;
            options = copyObj(options);

            var ctx = getDrawingContext(options),
			radius = options.radius,
			b2 = (options.border_width > 1) ? options.border_width : 0,
			rb2 = radius * 2 + b2,
			r = old_opt.radius,
			deg90 = Math.PI / 2;

            ctx.save();
            ctx.beginPath();
            ctx.arc(0, 0, radius, deg90, 0, true);
            ctx.arc(rb2, 0, radius, deg90 * 2, deg90, true);
            ctx.arc(rb2, rb2, radius, -deg90, deg90 * 2, true);
            ctx.arc(0, rb2, radius, 0, -deg90, true);
            ctx.closePath();
            ctx.clip();


            ctx.fillStyle = options.bg_color[2];
            ctx.fillRect(0, 0, r, r)

            ctx.fillStyle = options.bg_color[3];
            ctx.fillRect(r, 0, r, r);

            ctx.fillStyle = options.bg_color[0];
            ctx.fillRect(r, r, r, r);

            ctx.fillStyle = options.bg_color[1];
            ctx.fillRect(0, r, r, r);
            ctx.restore();

            if (options.border_width)
                strokeStar(ctx, options);

            return ctx.canvas.toDataURL();
        }

        /**
        * Возвращает ключ, по которому кэшируются отрисованные элементы
        * @param {getCornerParams()} cparams Параметры скругления блока
        * @param {HTMLElement} elem Элемент, для которого делаем скругление
        * @return {String}
        */
        function getCacheKey(cparams, elem) {
            var binded = getBindedProperties(elem);
            return [
			cparams.radius,
			cparams.bg_color.join('-'),
			cparams.real_border_width,
			cparams.border_color,
			cparams.use_shape,
			binded ? binded.id : 0
		].join(':');
        }

        /**
        * Создает CSS-правила для уголков определенного радиуса и цвета
        * @param {getCornerParams()} cparams Параметры скругления блока
        * @param {HTMLElement} elem Элемент, для которого делаем скругление
        * @return {String} Имя класса, которое нужно присвоить элементу
        */
        function createCSSRulesOpera(cparams, elem) {
            var cache_key = getCacheKey(cparams, elem),
			radius = cparams.radius,
			bw = cparams.real_border_width || 0,
			diff = (cparams.use_shape) ? bw - cparams.border_width : 0;

            // смотрим, делали ли правило с такими же параметрами
            if (!_corner_cache[cache_key]) {
                // создаем новое правило
                var cur_class = rule_prefix + corners_ss.cssRules.length;
                _corner_cache[cache_key] = cur_class;

                addRule('.' + cur_class + '>.' + base_class,
				'background-image: url("' + (cparams.use_shape ? drawStarShape(cparams) : drawStar(cparams)) + '");' +
				'width: ' + radius + 'px;' +
				'height: ' + (radius + diff) + 'px;'
			);

                var offset_x = -bw, offset_y = -bw;
                if (cparams.use_shape) {
                    offset_y = -radius - diff;
                    adjustBox(elem, cur_class, cparams);
                    addRule(
					'.' + cur_class + '>.' + base_class + '-tl, .' + cur_class + '>.' + base_class + '-bl',
					'width:auto;left:0;right:' + (radius - bw) + 'px;background-position:-' + radius + 'px 100%;'
				);
                    addRule('.' + cur_class + '>.' + base_class + '-bl', 'background-position:-' + radius + 'px 0;');
                }

                if (offset_x || offset_y) {
                    addRule('.' + cur_class + '>.' + base_class + '-tl', 'top:' + offset_y + 'px; left:' + offset_x + 'px');
                    addRule('.' + cur_class + '>.' + base_class + '-tr', 'top:' + offset_y + 'px; right:' + offset_x + 'px');
                    addRule('.' + cur_class + '>.' + base_class + '-bl', 'bottom:' + offset_y + 'px; left:' + offset_x + 'px');
                    addRule('.' + cur_class + '>.' + base_class + '-br', 'bottom:' + offset_y + 'px; right:' + offset_x + 'px');
                }
            }

            return _corner_cache[cache_key];
        }

        /**
        * Добавляет уголки элементу
        * @param {Element} elem
        */
        addCorners = function(elem, radius) {
            // если у элемента нет класса — значит, нет указания, какие уголки
            // нужно добавить
            if (!elem.className)
                return;

            // проверим, нужно ли добавлять элементы с уголками
            var dont_add = false;
            walkArray(elem.childNodes, function() {
                if (hasClass(this, base_class)) {
                    dont_add = true;
                    return false;
                }
            });

            var elem_class = createCSSRulesOpera(getCornerParams(elem, radius), elem);

            if (!dont_add)
            // добавляем уголки
                walkArray(['tl', 'tr', 'bl', 'br'], function() {
                    elem.appendChild(createElement('span', base_class + ' ' + base_class + '-' + this));
                });

            cleanUp(elem, elem_class + ' ' + base_class + '-init');
        };

        addDomReady(function() {
            /*
            * Одна из причин, по которой я ненавижу Оперу — это 
            * необходимость до сих пор вставлять подобные костыли, 
            * чтобы что-то отобразились на странице
            */
            document.documentElement.style.outline = 'none';
        });

        result.update = function() {
            applyCornersToArgs(arguments, function() {
                addCorners(cleanUp(this));
            });
        }
    } /**
 * Добавление уголков для IE
 * @author Sergey Chikuyonok (sc@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 * @include "common.js"
 */
    if (browser.msie && browser.version >= '9.0') {
        addCorners = function(elem, radius) {
            var selector = '.rc' + radius;
            if (!isProcessed(selector)) {
                addRule(selector, 'border-radius:' + radius + 'px');
                processed_rules[selector] = true;
            }
        }

        result.update = function() {
            applyCornersToArgs(arguments, function() {
                var m = re_class.exec(this.className || '');
                if (m)
                    addCorners(this, parseInt(m[1]));
            });
        }
    }
    if (browser.msie && browser.version < '9.0') {
        /*
        * Уголки для IE создаем через VML.
        * 
        * У IE в этом скрипте есть одно очень узкое место: динамическое добавление
        * CSS-правил в таблицу стилей (функция addRule()). Для увеличения 
        * производительности был применен следующий трюк: сначала, при первичной 
        * инициализации, весь CSS накапливается в переменной css_text, и после того,
        * как все необходимые правила для существующих блоков были созданы, 
        * накопленный CSS применяется к созданной таблице стилей. После этого 
        * функция addRule() уже указывает на метод corners_ss.addRule()  
        */

        _corner_cache.ix = 0;
        _corner_cache.created = {};

        var css_text = '',
		corner_types = {
		    tl: 0,
		    tr: 1,
		    br: 2,
		    bl: 3
		};

        var vml_class = 'vml-' + base_class; //использую именно класс, чтобы работало в IE8

        try {
            if (!document.namespaces["v"])
                document.namespaces.add("v", "urn:schemas-microsoft-com:vml");
        } catch (e) { }

        createStylesheet();
        var dot_class = '.' + base_class;
        corners_ss.cssText = "." + vml_class + " {behavior:url(#default#VML);display:inline-block;position:absolute}" +
		dot_class + "-init {position:relative;zoom:1;}" +
		dot_class + " {position:absolute; display:inline-block; zoom: 1; overflow:hidden}" +
		dot_class + "-tl ." + vml_class + "{flip: 'y'}" +
		dot_class + "-tr ." + vml_class + "{rotation: 180;right:1px;}" +
		dot_class + "-br ." + vml_class + "{flip: 'x'; right:1px;}";

        if (browser.version < 7) {
            corners_ss.cssText += dot_class + '-tr, ' + dot_class + '-br {margin-left: 100%;}';
            //				dot_class + ' .' + vml_class + '{position:absolute}' +
            //				dot_class + '-tr .' + vml_class + '{right: 0}';
        }

        addRule = function(selector, rules) {
            css_text += selector + '{' + rules + '}';
        };

        /**
        * Создает элемент со скругленным уголком. В функции используется
        * кэширование, то есть ранее созданный уголок дублируется, 
        * а не создается заново
        * @param {getCornerParams()} options Параметры рисования уголка 
        * @return {HTMLElement}
        */
        function createCornerElementIE(options) {
            var radius = options.radius,
			border_width = options.border_width,
			cache_key = radius + ':' + border_width + ':' + options.use_shape;

            if (!createCornerElementIE._cache[cache_key]) { // элемент еще не создан

                var multiplier = 10;

                var cv = createElement('v:shape');
                cv.className = vml_class;
                cv.strokeweight = border_width + 'px';
                cv.stroked = (border_width) ? true : false;
                var stroke = createElement('v:stroke');
                stroke.className = vml_class;
                stroke.joinstyle = 'miter';
                cv.appendChild(stroke);

                var w = radius, h = w;

                cv.style.width = w + 'px';
                cv.style.height = h + 'px';

                radius -= border_width / 2;
                radius *= multiplier;
                var bo = border_width / 2 * multiplier;
                var px = Math.round((radius + bo) / w);
                var rbo = radius + bo;

                cv.coordorigin = Math.round(px / 2) + ' ' + Math.round(px / 2);
                cv.coordsize = rbo + ' ' + rbo;

                var path = '';
                var max_width = rbo + px;

                if (options.use_shape) {
                    max_width = 2000 * multiplier;
                    path = 'm' + max_width + ',0 ns l' + bo + ',0  qy' + rbo + ',' + radius + ' l' + max_width + ',' + radius + ' e ';
                } else {
                    path = 'm0,0 ns l' + bo + ',0  qy' + rbo + ',' + radius + ' l' + rbo + ',' + rbo + ' l0,' + rbo + ' e ';
                }


                // stroke
                path += 'm' + bo + ',' + (-px) + ' nf l' + bo + ',0 qy' + rbo + ',' + radius + ' l ' + (max_width) + ',' + radius + ' e x';

                cv.path = path;

                createCornerElementIE._cache[cache_key] = cv;
            }

            return createCornerElementIE._cache[cache_key].cloneNode(true);
        }

        createCornerElementIE._cache = {};

        /**
        * Создает скругленный уголок
        * @param {getCornerParams()} cparams параметры уголка
        * @param {String} type Тип уголка (tl, tr, bl, br)
        */
        function drawCornerIE(cparams, type) {
            var cv = createCornerElementIE(cparams);
            cv.fillcolor = cparams.bg_color[corner_types[type]] || '#000';
            cv.strokecolor = cparams.border_color || '#000';

            var elem = createElement('span', base_class + ' ' + base_class + '-' + type);
            elem.appendChild(cv);

            return elem;
        }

        /**
        * Удаляет у элемента старые уголки
        * @param {HTMLElement} elem Элемент, у которого нужно удалить уголки 
        */
        function removeOldCorners(elem) {
            walkArray(elem.childNodes, function() {
                if (hasClass(this, base_class)) {
                    elem.removeChild(this);
                }
            });

            cleanUp(elem);
        }

        /**
        * Возвращает имя класса для переданных параметров. Используется для 
        * того, чтобы не плодить много разных классов для одних и тех же правил
        * @param {getCornerParams()} options Параметры рисования уголка
        * @return {String}
        */
        function getClassName(options) {
            var key = options.radius + ':' + (options.real_border_width || 0) + ':' + options.use_shape;
            if (!_corner_cache[key]) {
                _corner_cache[key] = rule_prefix + _corner_cache.ix++;
            }

            return _corner_cache[key];
        }

        /**
        * Создает CSS-правила для скругленных уголков
        * @param {getCornerParams()} options Параметры рисования уголка
        * @param {HTMLElement} elem Элемент, которому добавляются уголки
        * @param {Number} border_width Толщина бордюра
        */
        function createCSSRules(options, elem) {
            var radius = options.radius,
			border_width = options.real_border_width || 0,
			diff = (options.use_shape) ? options.real_border_width - options.border_width : 0;
            //		border_width += 10;

            //		corners_ss.disabled = true;

            var class_name = getClassName(options);
            if (!_corner_cache.created[class_name]) {
                // такое правило еще не создано в CSS, создадим его
                var prefix = (browser.version < 7)
							? '.' + class_name + ' .' + base_class  // IE6 
							: '.' + class_name + '>.' + base_class; // IE7+

                var offset_x = -border_width,
				offset_y = -1 - border_width;


                addRule(prefix, 'width:' + (radius + border_width + 1) + 'px;height:' + (radius + 1) + 'px');

                if (options.use_shape) {
                    offset_y = -radius - 1 - diff;
                    var left_adjust = radius + options.border_width * 2 + diff;
                    adjustBox(elem, class_name, options);
                    var clip_size = Math.max(radius - border_width * 2, 0),
					pad_size = Math.min(radius - border_width * 2, 0) * -1;

                    if (browser.version < 7) {
                        pad_size += parseInt(getStyle(elem, 'padding-left') || 0) + parseInt(getStyle(elem, 'padding-right') || 0);
                    }

                    var css_rules = 'width:100%;clip:rect(auto auto auto ' + clip_size + 'px);padding-right:' + pad_size + 'px;left:' + (-border_width - clip_size) + 'px;';
                    addRule(prefix + '-tl', css_rules + 'top:' + offset_y + 'px;');
                    addRule(prefix + '-tl .' + vml_class, 'left:' + clip_size + 'px');

                    addRule(prefix + '-bl', css_rules + 'bottom:' + offset_y + 'px;');
                    addRule(prefix + '-bl .' + vml_class, 'left:' + clip_size + 'px');
                } else {
                    addRule(prefix + '-tl', 'left:' + offset_x + 'px;top:' + offset_y + 'px;');
                    addRule(prefix + '-bl', 'left:' + offset_x + 'px;bottom:' + offset_y + 'px;');
                }

                if (browser.version < 7) {
                    offset_x = -radius + (border_width ? radius % 2 - border_width % 2 : -radius % 2);

                    addRule(prefix + '-tr', 'left:' + offset_x + 'px;top:' + offset_y + 'px;');
                    addRule(prefix + '-br', 'left:' + offset_x + 'px;bottom:' + offset_y + 'px;');
                } else {
                    addRule(prefix + '-tr', 'right:' + offset_x + 'px;top:' + offset_y + 'px;');
                    addRule(prefix + '-br', 'right:' + offset_x + 'px;bottom:' + offset_y + 'px;');
                }

                _corner_cache.created[class_name] = true;
            }
        }

        addCorners = function(elem, radius) {
            var cparams = getCornerParams(elem, radius);

            createCSSRules(cparams, elem);

            // теперь добавляем сами уголки в элемент
            walkArray(['tl', 'tr', 'bl', 'br'], function() {
                elem.appendChild(drawCornerIE(cparams, this));
            });


            // говорим, что все добавилось
            elem.className += ' ' + getClassName(cparams) + ' ' + base_class + '-init';

        };

        result.update = function() {
            applyCornersToArgs(arguments, function() {
                removeOldCorners(this);
                addCorners(this);
            });
        };

        addDomReady(function() {
            corners_ss.cssText += css_text;
            css_text = '';
            addRule = corners_ss.addRule;
        });
    }; return result;
})();